home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Utilities / Winter Shell 1.0d2 / Source / Libraries / ProgressLib / ProgressLib.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-01-17  |  12.6 KB  |  386 lines  |  [TEXT/KAHL]

  1. /* Functions for displaying a progress dialog (as described in IM-VI, p2-25)
  2.     and handling events during long execution of lengthy algorithms.
  3.     
  4.     94/01/17 aih
  5.     - When in the background, uses a value of 5 ticks for RUN_TICKS
  6.     instead of a value of 0 ticks. This makes background operation
  7.     a bit speedier without overly slowing down the foreground application.
  8.     
  9.     93/12/16 aih
  10.     - progress dialog's position is saved/restored to/from preferences file
  11.     
  12.     93/11/03 aih
  13.     - converted to use a handle instead of a pointer
  14.     
  15.     93/10/27 aih
  16.     - added functions for reseting the dialog and setting the prompt
  17.     
  18.     93/10/22 aih
  19.     - removed ProgressInit, global variables, and saving/restoring progress
  20.     dialog's position to a global variable
  21.     
  22.     93/10/20 aih
  23.     - added support for modeless dialogs
  24.     
  25.     93/10/13 AIH
  26.     - Added special meaning to value of 0 for idle parameter
  27.     - Added check for command-period even if the progress dialog isn't
  28.     open yet
  29.     - Added canceled field to progress structure
  30.     
  31.     93/03/19 AIH
  32.     - Changed ProgressLibBegin to ProgressInit
  33.     
  34.     93/03/17 AIH
  35.     - Added user specifiable dialog items
  36.     
  37.     93/03/13 AIH
  38.     - Changed library name from "EventPeriodicLib" to "ProgressLib"
  39.     
  40.     93/03/06 AIH
  41.     - Fixed cursor spinning interval
  42.     
  43.     92/02/29 AIH
  44.     - Made into a separate file. */
  45.  
  46. #include <stdarg.h>
  47. #include <stdio.h>
  48. #include <string.h>
  49. #include <SysEqu.h>
  50. #include "DialogLib.h"
  51. #include "DialogModalLib.h"
  52. #include "DrawLib.h"
  53. #include "EventLib.h"
  54. #include "KeyLib.h"
  55. #include "MathLib.h"
  56. #include "MemoryLib.h"
  57. #include "PreferencesLib.h"
  58. #include "ProgressLib.h"
  59. #include "RectangleLib.h"
  60. #include "ResourceLib.h"
  61. #include "ResourceConstantsLib.h"
  62. #include "StringLib.h"
  63. #include "TimeLib.h"
  64. #include "WindowLib.h"
  65.  
  66. #define Ticks() (*(long *) Ticks) /* faster than calling TickCount() */
  67.  
  68. /* While the application is doing some lengthy operation it should 
  69.     periodically call EventAvail, GNE, or WNE to find out if there are any events it
  70.     should handle by entering the event loop and to allow other applications
  71.     to receive CPU time. The application should also display a progress dialog and
  72.     should spin a cursor. The following intervals have been determined
  73.     empirically to be approproriate for each of these actions. They should
  74.     give enough time to other applications without unduly slowing down the
  75.     current application. Notice that when the application is in the background
  76.     it gives significantly more time to other applications, since presumabely
  77.     the user cares more about the response of the foreground application than
  78.     the time it takes for an application to execute in the background. */
  79. #define EVENT_TICKS    (30)    /* interval to check for events */
  80. #define CURS_TICKS    (30)    /* interval to spin the cursor */
  81. #define UPDT_TICKS    (60)    /* interval to update dialog */
  82. #define START_TICKS    (120)    /* interval before showing dialog */
  83. #define RUN_TICKS        (gInBackground ? 5 : 30)    /* interval before doing any of the above */
  84.  
  85. /* true if valid progress */
  86. Boolean ProgressValid(ProgressHandle progress)
  87. {
  88.     return(HandleValidSize(progress, sizeof(ProgressType)));
  89. }
  90.  
  91. /* Update function for progress indicator in progress event loop's
  92.     dialog. This draws a black bar which advances towards the right,
  93.     in a manner very similar to the way that Finder 6.0 indicates
  94.     how many files it has copied. */
  95. void ProgressUpdate(ProgressHandle progress)
  96. {
  97.     Rect box;
  98.     Rect fill;
  99.     GrafPtr port;
  100.     PenState pen;
  101.     
  102.     require(ProgressValid(progress));
  103.     if ((**progress).dlg) {
  104.         GetPort(&port);
  105.         SetPort((**progress).dlg);
  106.         GetPenState(&pen);
  107.         PenNormal();
  108.         DlgBox((**progress).dlg, (**progress).useritm, &box);
  109.         FrameRect(&box);
  110.         fill = box;
  111.         if ((**progress).maxcnt > 0 && (**progress).maxcnt > (**progress).count) {
  112.             fill.right = box.left +
  113.                 ((float) (**progress).count / (**progress).maxcnt) * RectWidth(&box);
  114.         }
  115.         if (RectWidth(&fill) > 1) {
  116.             InsetRect(&fill, 1, 1);
  117.             PaintRect(&fill);
  118.         }
  119.         fill.top = box.top;
  120.         fill.left = fill.right;
  121.         fill.bottom = box.bottom;
  122.         fill.right = box.right;
  123.         InsetRect(&fill, 1, 1);
  124.         EraseRect(&fill);
  125.         SetPenState(&pen);
  126.         SetPort(port);
  127.         (**progress).lastUpdate = Ticks();
  128.     }
  129.     ensure(ProgressValid(progress));
  130. }
  131.  
  132. /* Open the progress dialog. If 'modal' is false then a movable modal dialog
  133.     is created (actually, whatever's specified in the 'DLOG' resource),
  134.     while if 'modal' is true then a noGrowDocProc dialog is created. */
  135. void ProgressOpenDialog(ProgressHandle progress, Boolean modal)
  136. {
  137.     volatile DialogTHndl template = NULL;
  138.     DialogPtr dlg = NULL;
  139.     
  140.     require(ProgressValid(progress));
  141.     require(! (**progress).dlg);
  142.     TRY {
  143.         
  144.         /* force dialog to be non-modal if necessary */
  145.         (**progress).modal = modal;
  146.         if (! (**progress).modal) {
  147.             template = (DialogTHndl) ResGet('DLOG', RLD_BUSY);
  148.             (void) HandleNoPurge(template);
  149.             (**template).procID = noGrowDocProc;
  150.         }
  151.         
  152.         /* create and position the dialog */
  153.         dlg = DlgBegin(RLD_BUSY);
  154.         (**progress).dlg = dlg;
  155.         WinRegister((**progress).dlg, progress, ProgressEventTable());
  156.         DlgPosition((**progress).dlg);
  157.         check(! (**progress).appdlg);
  158.         WinZoomRestore((**progress).dlg, PrefsFile(), PREFS_PROGRESS_POSITION);
  159.         
  160.         /* force check for update and activate events for dialog */
  161.         (**progress).lastRun = 0;
  162.                 
  163.         /* set the prompt if one was provided */
  164.         if (*(**progress).str) {
  165.             Str255 str;
  166.             BlockMove((**progress).str, str, *(**progress).str + 1);
  167.             DlgTextSet((**progress).dlg, (**progress).textitm, p2cstr(str));
  168.         }
  169.     } CLEANUP {
  170.         if (template) ResRelease((Handle) template);
  171.     } CATCH {
  172.         DlgEnd((**progress).dlg);
  173.         (**progress).dlg = NULL;
  174.     } ENDTRY;
  175.     ensure((**progress).modal == WinIsModal((**progress).dlg));
  176.     ensure(ProgressValid(progress));
  177. }
  178.  
  179. /* set the cursor to the arrow cursor in response to user events */
  180. static void AdjustCursor(ProgressHandle progress, EventRecord *event)
  181. {
  182.     if (event->what == mouseDown ||
  183.          event->what == keyDown ||
  184.          event->what == autoKey)
  185.     {
  186.         (**progress).cursorInterval = START_TICKS;
  187.         DrawCursor(arrowCursor);
  188.     }
  189. }
  190.  
  191. /* The following functions are called by ProgressRun. See its description
  192.     for some details. */
  193.  
  194. /* process events */
  195. static void ProgressEvents(ProgressHandle progress)
  196. {
  197.     EventRecord event;
  198.     int mask = everyEvent;
  199.  
  200.     /* Run the event loop if there are any pending events. If the event
  201.         is a user event and the dialog is (or would be) modal, then the progress
  202.         dialog must exist, since otherwise the menus and other windows might not
  203.         be properly disabled; the simplest solution is to handle update and
  204.         activate events if the dialog doesn't exist, since these events are
  205.         not generated directly by the user, and they have a high priority which
  206.         could lock out other applications from receiving CPU time. */
  207.     if (! gInBackground && Ticks() - (**progress).lastEvent <= EVENT_TICKS)
  208.         return;
  209.     if (! (**progress).dlg)
  210.         mask = keyDownMask + updateMask + activMask;
  211.     if (EventAvail(mask, &event)) {
  212.         while (EventGet(mask, &event, 0, NULL)) {
  213.             if (! (**progress).dlg && event.what == keyDown && KeyCancel(&event)) {
  214.                 (**progress).canceled = true;
  215.                 break;
  216.             }
  217.             AdjustCursor(progress, &event);
  218.             EventExecute(&event);
  219.         }
  220.         /* check for cancel */
  221.         if ((**progress).dlg && DlgClicked((**progress).dlg) == PROGRESS_STOP)
  222.             (**progress).canceled = true;
  223.         if ((**progress).canceled)
  224.             FailOSErr(userCanceledErr);
  225.     }
  226.     (**progress).lastEvent = Ticks();
  227. }
  228.  
  229. /* spin the cursor */
  230. static void ProgressSpin(ProgressHandle progress)
  231. {
  232.     if (! gInBackground && Ticks() - (**progress).lastCursor > (**progress).cursorInterval) {
  233.         DrawCursor((**progress).cursorIndex + RLCURS_BUSY);
  234.         (**progress).cursorIndex = ((**progress).cursorIndex + 1) % RLCURS_BUSY_COUNT;
  235.         (**progress).cursorInterval = CURS_TICKS;
  236.         (**progress).lastCursor = Ticks();
  237.     }
  238. }
  239.  
  240. /* show the dialog and update the progress indicator */
  241. static void ProgressShow(ProgressHandle progress)
  242. {
  243.     require(ProgressValid(progress));
  244.     if ((**progress).visible) {
  245.         /* periodically update dialog */
  246.         if (Ticks() - (**progress).lastUpdate > UPDT_TICKS)
  247.             ProgressUpdate(progress);
  248.     }
  249.     else if (Ticks() - (**progress).start > START_TICKS) {
  250.         /* create or show dialog if operation is taking longer than a few seconds */
  251.         ProgressUpdate(progress);
  252.         if ((**progress).dlg)
  253.             WinSelect((**progress).dlg);
  254.         else
  255.             ProgressOpenDialog(progress, true);
  256.         WinShow((**progress).dlg);
  257.         (**progress).visible = true;
  258.     }
  259. }
  260.  
  261. /* Call this when once every time through a portion of a lengthy operation.
  262.     After a minimum number of ticks have elapsed since the progress event
  263.     loop was initialized a dialog (default is movable modal) is displayed
  264.     asking the user to wait (the application must supply a dialog ID when
  265.     initializing the event library). The 'done' parameter is used to draw
  266.     the progress bar and should be the amount of work done so far, or -1 if
  267.     every call to this function counts as one unit of work, or 0 to just
  268.     process events without changing the indicator. The cursor
  269.     will be spinned about twice a second and EventAvail is called periodically
  270.     to give other applications time. Any pending events are handled as
  271.     soon as EventAvail reports them. EventAvail is used since it is more
  272.     efficient than GetNextEvent, which in turn would be more efficient
  273.     than WaitNextEvent. */
  274. void ProgressRun(ProgressHandle progress, long done)
  275. {
  276.     require(ProgressValid(progress));
  277.     if (done == -1L)
  278.         (**progress).count++;
  279.     else if (done > 0)
  280.         (**progress).count = done;
  281.     if (Ticks() - (**progress).lastRun > RUN_TICKS) {
  282.         ProgressEvents(progress);
  283.         ProgressSpin(progress);
  284.         ProgressShow(progress);
  285.         (**progress).lastRun = Ticks();
  286.     }
  287.     ensure(ProgressValid(progress));
  288. }
  289.  
  290. /* set the string displayed in the progress dialog */
  291. void ProgressPrompt(ProgressHandle progress, const char *fmt, ...)
  292. {
  293.     va_list ap;
  294.     CStr255 str;
  295.     
  296.     require(ProgressValid(progress));
  297.     va_start(ap, fmt);
  298.     vsprintf(str, fmt, ap);
  299.     va_end(ap);
  300.     if ((**progress).dlg)
  301.         DlgTextSet((**progress).dlg, PROGRESS_TEXT, str);
  302.     c2pstr(str);
  303.     BlockMove(str, (**progress).str, *str + 1);
  304.     ensure(ProgressValid(progress));
  305. }
  306.  
  307. /* Reset the progress dialog. Useful when running an operation after a
  308.     previous operation and when you don't want to close and then reopen
  309.     the progress dialog. */
  310. void ProgressReset(ProgressHandle progress, size_t max)
  311. {
  312.     require(ProgressValid(progress));
  313.     (**progress).count = 0;
  314.     (**progress).maxcnt = max;
  315.     (**progress).canceled = false;
  316.     (**progress).lastRun = 0;
  317.     (**progress).lastEvent = 0;
  318.     (**progress).lastUpdate = 0;
  319.     (**progress).lastCursor = 0;
  320.     if ((**progress).dlg)
  321.         ProgressUpdate(progress);
  322.     ensure(ProgressValid(progress));
  323. }
  324.  
  325. /* Call this before calling ProgressRun. The 'max'
  326.     parameter should be your best estimate of the number of times
  327.     ProgressRun will be called. A non-zero value for 'max' will cause
  328.     a black progress bar to be drawn in the progress event loop's dialog.
  329.     The 'dlg' parameter should be a dialog which will be displayed while
  330.     the application is executing its progress event loop, or NULL to use
  331.     the default dialog. Item number 1 in the dialog should be a user item
  332.     which will be used to display the progress bar. The 'str' parameter
  333.     should contain a string to display in item 3 (a statText item)
  334.     of the dialog, or NULL if no string should be used. The 'useritm'
  335.     and 'textitm' fields should contain the item numbers of items
  336.     to display the progress bar and prompt string; if they're 0 then
  337.     the default items are used. */
  338. ProgressHandle ProgressBegin(size_t max, CStr255 str, DialogPtr dlg,
  339.     short useritm, short textitm)
  340. {
  341.     ProgressHandle progress = NULL;
  342.     
  343.     require(! dlg || DlgValid(dlg));
  344.     require(! str || StrValid(str, sizeof(CStr255)));
  345.     TRY {
  346.         /* initialize variables */
  347.         progress = HandleBeginClear(sizeof(ProgressType));
  348.         (**progress).cursorInterval = CURS_TICKS;
  349.         (**progress).start = Ticks();
  350.         (**progress).maxcnt = max;
  351.         (**progress).dlg = dlg;
  352.         (**progress).appdlg = (dlg != NULL);
  353.         (**progress).useritm = PROGRESS_USER;
  354.         (**progress).textitm = PROGRESS_TEXT;
  355.         if (useritm) (**progress).useritm = useritm;
  356.         if (textitm) (**progress).textitm = textitm;
  357.         if (str) ProgressPrompt(progress, str);        
  358.         if (! dlg || WinIsModal(dlg)) (**progress).modal = true;
  359.         if (dlg) WinRegister(dlg, progress, ProgressEventTable());
  360.     } CATCH {
  361.         ProgressEnd(progress);
  362.     } ENDTRY;
  363.     ensure(ProgressValid(progress));
  364.     return(progress);
  365. }
  366.  
  367. /* Cleanup after a progress event loop by disposing of the progress dialog
  368.     (if it was created by the event library) and reseting all the variables.
  369.     You must call this function when you've finished with the progress event
  370.     loop. */
  371. void ProgressEnd(ProgressHandle progress)
  372. {
  373.     if (progress) {
  374.         if ((**progress).dlg) {
  375.             WinUnregister((**progress).dlg, progress);
  376.             if (! (**progress).appdlg) {
  377.                 WinZoomSave((**progress).dlg, PrefsFile(), PREFS_PROGRESS_POSITION);
  378.                 DlgEnd((**progress).dlg);
  379.             }
  380.         }
  381.         HandleEnd(progress);
  382.         progress = NULL;
  383.     }
  384.     ensure(! ProgressValid(progress));
  385. }
  386.